#Copyright (C) Nial Peters 2012
#
#This file is part of AvoScan.
#
#AvoScan is free software: you can redistribute it and/or modify
#it under the terms of the GNU General Public License as published by
#the Free Software Foundation, either version 3 of the License, or
#(at your option) any later version.
#
#AvoScan is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with AvoScan.  If not, see <http://www.gnu.org/licenses/>.
"""
IMPORTANT - The AvoScan watcher module is going to be removed soon! It will
be replaced by the watcher module in the doas package, which will be more
robust and less hackey (particularly the windows implementation).

The timestamps recorded in the spectra files are often only to the nearest
second. This introduces large uncertainties when we come to assign angles
to each spectrum. This module provides watcher classes which monitor the
output directory of the spectrometer control program looking for new 
spectrum files. As the files appear, the watcher classes record their creation
time (with millisecond precision) in a separate file. 
"""
import sys
import os.path
import datetime
import calendar
import time
import threading

def can_watch_directories():
    """
    Returns true if there is a directory watching implementation for
    this system type.
    """
    #check if we have better than 1 second resolution on the system clock    
    t1 = datetime.datetime.utcnow()
    time.sleep(0.5)
    t2 = datetime.datetime.utcnow()
    
    if abs((0.5 - (t2 - t1).microseconds * 1e-6)) > 0.1:
        return False
    
    if sys.platform == 'linux2':
        try:
            import pyinotify
        except ImportError:
            return False
        return True
    
    elif sys.platform == 'win32':
        try:
            import win32file
            import win32con 
            return True
        except ImportError:
            return False
    else:
        return False
    

def create_dir_watcher(dir_name):
    """
    Returns a watcher class suitable for this system type, or
    None if no implementation is available.
    """
    if can_watch_directories():
    
        if sys.platform == 'linux2':
            return LinuxDirectoryWatcher(dir_name)
        elif sys.platform == 'win32':
            return WindowsDirectoryWatcher(dir_name)
        else:
            raise RuntimeError, "This should never happen!"
    else:
        return None
    

class DirectoryWatcherBase:
    """
    Base class for watcher classes.
    """
    def __init__(self, dir_name):
        self.dir_to_watch = dir_name
        times_filename = os.path.basename(dir_name.rstrip(os.path.sep))
        self.times_file = os.path.join(dir_name,os.path.pardir,times_filename+"_spectra_times.txt")
        if not os.path.exists(self.times_file):
            with open(self.times_file, "w") as ofp:
                ofp.write("# This file was created by AvoScan. It documents the creation\n"+
                          "# times of all files created in "+dir_name+" and its subdirectories."
                          "\n#\n# Filename\t\tCreation Time (POSIX timestamp)\n")
         
    def _log_time(self, filename, time_):
        with open(self.times_file, "a") as ofp:
            ofp.write(filename+"\t"+str(time_)+"\n")
    
    def start(self):
        raise NotImplementedError
    
    def stop(self):
        raise NotImplementedError 

try:
    import pyinotify
    class LinuxDirectoryWatcher(DirectoryWatcherBase,pyinotify.ProcessEvent):
        """
        Watcher class using inotify.
        """
        def __init__(self, dir_name):
            DirectoryWatcherBase.__init__(self, dir_name)
            
            #note - no need to call ProcessEvent.__init__
            watch_manager = pyinotify.WatchManager()
            mask = pyinotify.EventsCodes.ALL_FLAGS['IN_CREATE']
            watch_manager.add_watch(dir_name, mask, rec=True)
                    
            self.notifier = pyinotify.ThreadedNotifier(watch_manager, self)
        
        
        def start(self):
            self.notifier.start()
     
        
        def stop(self):
            self.notifier.stop()
    
            
        def process_IN_CREATE(self, event):
            t = datetime.datetime.utcnow()
            self._log_time(os.path.join(event.path, event.name), calendar.timegm(t.timetuple()) + t.microsecond*1e-6)

except ImportError:
    pass


try:
    import win32file
    import win32con
    class WindowsDirectoryWatcher(DirectoryWatcherBase):    
        """
        Watcher class for Windows systems.
        The code for this class is based on Tim Golden's website:
        http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.html
        """
        def __init__(self, dir_name):
            DirectoryWatcherBase.__init__(self, dir_name)
            
            self.hDir = win32file.CreateFile(dir_name, 
                                        0x0001,
                                        win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
                                        None,
                                        win32con.OPEN_EXISTING,
                                        win32con.FILE_FLAG_BACKUP_SEMANTICS,
                                        None)
            
            self.worker_thread = None
            self.stay_alive = True
     
        
        def start(self):
            self.stay_alive = True
            self.worker_thread = threading.Thread(target=self.__do_watching)
            self.worker_thread.start()
      
        
        def stop(self):
            self.stay_alive = False
            
            if self.worker_thread is not None:
                #create fake file
                fp = open(os.path.join(self.dir_to_watch,"avoscan_watcher_exit.exit"),"w")
                fp.close()
                self.worker_thread.join()
                #delete fake file
                os.remove(os.path.join(self.dir_to_watch,"avoscan_watcher_exit.exit"))
            
            self.worker_thread = None
       
        
        def __do_watching(self):        
            while self.stay_alive:
                results = win32file.ReadDirectoryChangesW(self.hDir,
                                                          1024,
                                                          True,
                                                          win32con.FILE_NOTIFY_CHANGE_FILE_NAME,
                                                          None,
                                                          None)
                t = datetime.datetime.utcnow()
    
                for action, filename in results:
                    if action != 1:
                        #then it is not a file creation event
                        continue
                    
                    if filename == "avoscan_watcher_exit.exit":
                        break
                    
                    self._log_time(os.path.join(self.dir_to_watch, filename), calendar.timegm(t.timetuple()) + t.microsecond*1e-6)
   
except ImportError:
    pass   
    
    
    